#pragma once

#include <queue>
#include <memory>
#include <mutex>
#pragma warning(disable:4251)

namespace qb{
    template<typename T>
    class threadsafeQueue
    {
    public:
        void push(const T& item)
        {
            emplace(item);
        }

        void push(T&& item)
        {
            emplace(std::move(item));
        }

        template<typename... Args>
        void emplace(Args&&... args)
        {
            std::unique_lock<std::mutex> lock(m_mutex);
            m_queue.emplace(std::forward<Args>(args)...);
            lock.unlock();
        }

        bool empty() const
        {
            std::unique_lock<std::mutex> lock(m_mutex);
            return m_queue.empty();
        }

        bool try_pop(T& popped_value)
        {
            std::unique_lock<std::mutex> lock(m_mutex);
            if (m_queue.empty())
            {
                return false;
            }

            popped_value = std::move(m_queue.front());
            m_queue.pop();
            return true;
        }

        void pop(T& popped_value)
        {
            std::unique_lock<std::mutex> lock(m_mutex);
            popped_value = std::move(m_queue.front());
            m_queue.pop();
        }

        // Provides only basic exception safety guarantee when RVO is not applied.
        T pop()
        {
            std::unique_lock<std::mutex> lock(m_mutex);
            auto item = std::move(m_queue.front());
            m_queue.pop();
            return item;
        }

    private:
        std::queue<T> m_queue;
        mutable std::mutex m_mutex;
    };


    template <typename T>
    class smartptr_queue
    {
    private:
        mutable std::mutex mx;
        std::queue<std::shared_ptr<T>> data_queue;

    public:
        smartptr_queue() = default;

        void push(T new_value)
        {
            std::shared_ptr<T> data(std::make_shared<T>(std::move(new_value)));
            std::lock_guard<std::mutex> lg(mx);
            data_queue.push(std::move(data));
        }

        void pop(T& value)
        {
            std::unique_lock<std::mutex> lg(mx);
            value = std::move(*data_queue.front());
            data_queue.pop();
        }

        std::shared_ptr<T> pop()
        {
            std::unique_lock<std::mutex> lg(mx);
            std::shared_ptr<T> res = data_queue.front();
            data_queue.pop();
            return res;
        }

        bool try_pop(T& value)
        {
            std::lock_guard<std::mutex> lg(mx);
            if (data_queue.empty())
            {
                return false;
            }
            value = std::move(*data_queue.front());
            data_queue.pop();
            return true;
        }

        std::shared_ptr<T> try_pop()
        {
            std::lock_guard<std::mutex> lg(mx);
            if (data_queue.empty())
            {
                return std::shared_ptr<T>();
            }
            std::shared_ptr<T> res = data_queue.front();
            data_queue.pop();
            return res;
        }

        bool empty() const
        {
            std::lock_guard<std::mutex> lg(mx);
            return data_queue.empty();
        }
    };

}//namespace qb;
